﻿//-----------------------------------------------------------------------
// <copyright company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.DevDiv.Wpf.Samples
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Markup;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Threading;

    /// <summary>
    /// Lightweight Adorner
    /// </summary>
    /// <remarks>
    /// The builtin adorner system uses LayoutUpdated to keep adorners in sync with adorned elements
    /// Meaning you pay an adorner tax every time any element moves
    /// This system installs an adorner on only the adorned element you you pay only the cost for elements
    /// that actually change
    /// I would love to have used Decorator as my base class but it does not support overriding the behaviour of the Child property setter
    /// </remarks>
    [ContentProperty("Child")]
    public abstract class LocalAdornerBase : FrameworkElement
    {
        /// <summary>
        /// Element being adorned
        /// </summary>
        public UIElement Child
        {
            get { return this.child; }
            set
            {
                if (!object.ReferenceEquals(value, this.child))
                {
                    if (this.child != null)
                    {
                        this.Children.Remove(this.child);
                    }

                    this.child = value;

                    if (value != null)
                    {
                        this.Children.Insert(0, value);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the adorning element
        /// </summary>
        /// <returns>The adorning element</returns>
        /// <CodeAnalysisSuppression>
        /// Keeping method syntax to allow derived classes optionally expose XAML friendly property getter
        /// This means having a public property getter
        /// </CodeAnalysisSuppression>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        protected UIElement GetAdornment()
        {
            return this.adornment;
        }

        /// <summary>
        /// Sets the adorning element
        /// </summary>
        /// <remarks>
        /// You may need to call ShowAdornment() to make the new adornment visible
        /// </remarks>
        /// <param name="value">New adorning element (may be null to clear adornments)</param>
        protected void SetAdornment(UIElement value)
        {
            if (!object.ReferenceEquals(value, this.adornment))
            {
                if (this.adornment != null && this.adornmentHost != null)
                {
                    this.adornmentHost.Children.Clear();
                }

                this.adornment = value;
            }
        }

        /// <summary>
        /// If an adornment has been set makes it visible
        /// </summary>
        protected void ShowAdornment()
        {
            if (this.adornment != null)
            {
                if (this.adornmentHost == null)
                {
                    EnsureAdornmentHost().Children.Add(this.adornment);
                }
                else
                {
                    if (!this.adornmentHost.Children.Contains(this.adornment))
                    {
                        this.adornmentHost.Children.Add(this.adornment);
                    }
                }
            }
        }

        /// <summary>
        /// Hides any adornment
        /// </summary>
        protected void HideAdornment()
        {
            if (this.adornmentHost != null)
            {
                this.adornmentHost.Children.Clear();
            }
        }

        protected override int VisualChildrenCount
        {
            get
            {
                return (this.children != null) ? this.children.Count : 0;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (this.children == null)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            return this.children[index];
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            Size desiredSize = new Size();

            if (this.child != null)
            {
                this.child.Measure(availableSize);
                desiredSize = Max(desiredSize, this.child.DesiredSize);
            }

            if (this.adornment != null)
            {
                this.adornment.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }

            return desiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (this.child != null)
            {
                this.child.Arrange(new Rect(finalSize));
            }

            if (this.adornment != null)
            {
                this.adornment.Arrange(new Rect(this.adornment.DesiredSize));
            }

            return finalSize;
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            this.Dispatcher.BeginInvoke(
                DispatcherPriority.Normal,
                (DispatcherOperationCallback)UpdateAdornmentCallback,
                null);
        }

        /// <summary>
        /// Called when the adorned element has changed
        /// This is where subclasses would potentially refresh the state of their adorner
        /// </summary>
        protected abstract void OnAdornedElementUpdated(EventArgs e);

        protected void SetAdornmentHostOffset(Vector newOffset)
        {
            if (this.adornmentHost != null)
            {
                this.adornmentHost.Offset = newOffset;
            }
        }

#if WPF_4_0
        protected void SetAdornmentHostCacheMode(CacheMode newCacheMode)
        {
            if (this.adornmentHost != null)
            {
                this.adornmentHost.CacheMode = newCacheMode;
            }
        }
#endif

        protected void SetAdornmentHostClip(Geometry newClip)
        {
            if (this.adornmentHost != null)
            {
                this.adornmentHost.Clip = newClip;
            }
        }

#if WPF_4_0
        protected void SetAdornmentHostEffect(Effect newEffect)
        {
            if (this.adornmentHost != null)
            {
                this.adornmentHost.Effect = newEffect;
            }
        }
#endif

        protected void SetAdornmentHostOpacity(double newOpacity)
        {
            EnsureAdornmentHost().Opacity = newOpacity;
        }

        protected void SetAdornmentHostOpacityMask(Brush newOpacityMask)
        {
            EnsureAdornmentHost().OpacityMask = newOpacityMask;
        }

        protected void SetAdornmentHostTransform(Transform newTransform)
        {
            EnsureAdornmentHost().Transform = newTransform;
        }

        protected void SetAdornmentHostXSnappingGuidelines(DoubleCollection newXSnappingGuidelines)
        {
            EnsureAdornmentHost().XSnappingGuidelines = newXSnappingGuidelines;
        }

        protected void SetAdornmentHostYSnappingGuidelines(DoubleCollection newYSnappingGuidelines)
        {
            EnsureAdornmentHost().YSnappingGuidelines = newYSnappingGuidelines;
        }

        private ContainerVisual EnsureAdornmentHost()
        {
            if (this.adornmentHost == null)
            {
                this.adornmentHost = new ContainerVisual();
                this.Children.Add(this.adornmentHost);
            }

            return this.adornmentHost;
        }

        private VisualCollection Children
        {
            get
            {
                if (this.children == null)
                {
                    this.children = new VisualCollection(this);
                }

                return this.children;
            }
        }

        private object UpdateAdornmentCallback(object arg)
        {
            OnAdornedElementUpdated(EventArgs.Empty);
            return null;
        }

        private static Size Max(Size a, Size b)
        {
            return new Size(Math.Max(a.Width, b.Width), Math.Max(a.Height, b.Height));
        }

        private UIElement child;
        private UIElement adornment;
        private ContainerVisual adornmentHost;
        private VisualCollection children;
    }
}